package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import lombok.SneakyThrows;
import org.apache.lucene.search.join.ScoreMode;
import org.aspectj.weaver.ast.Var;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.swing.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author atguigu-mqx
 */
@Service
public class SearchServiceImpl  implements SearchService {

    //  调用service-product 微服务提供的远程调用！
    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public void upperGoods(Long skuId) {
        //  先创建一个Goods 对象
        Goods goods = new Goods();
        //  介绍有关于操作 es 的相关类！ 自定义一个接口，这个接口继承一个 ElasticsearchRepository
        /*
        利用远程调用获取数据： 给 goods 赋值！
        Sku基本信息（详情业务已封装了接口）
        Sku分类信息（详情业务已封装了接口）
        Sku的品牌信息
        Sku对应的平台属性
         */
        //  给goods 赋值
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);

        goods.setId(skuInfo.getId());
        goods.setTitle(skuInfo.getSkuName()); // skuName
        goods.setPrice(skuInfo.getPrice().doubleValue()); // 数据类型转换
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        goods.setCreateTime(new Date());

        //  品牌：
        BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());

        goods.setTmId(trademark.getId());
        goods.setTmName(trademark.getTmName());
        goods.setTmLogoUrl(trademark.getLogoUrl());

        //  分类数据
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());

        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory3Id(categoryView.getCategory3Id());
        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Name(categoryView.getCategory3Name());

        //  private List<SearchAttr> attrs;
        //  给平台属性名称 平台属性值名称 平台属性Id
        //  根据skuId 获取到对应的数据
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);

        //  声明一个集合对象
        //        List<SearchAttr> searchAttrs = new ArrayList<>();
        //        //  判断集合是非为空
        //        if (!CollectionUtils.isEmpty(attrList)){
        //            for (BaseAttrInfo baseAttrInfo : attrList) {
        //                //  声明一个对象
        //                SearchAttr searchAttr = new SearchAttr();
        //                searchAttr.setAttrId(baseAttrInfo.getId());
        //                searchAttr.setAttrName(baseAttrInfo.getAttrName());
        //                //  赋值属性值名称！
        //                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
        //                searchAttrs.add(searchAttr);
        //            }
        //        }
        //        //  赋值平台属性数据！
        //        goods.setAttrs(searchAttrs);

        //  使用拉姆达表达式
        List<SearchAttr> searchAttrList = attrList.stream().map((baseAttrInfo) -> {
            //  声明一个对象
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(baseAttrInfo.getId());
            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            //  赋值属性值名称！
            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            return searchAttr;
        }).collect(Collectors.toList());

        //  赋值平台属性数据！
        goods.setAttrs(searchAttrList);

        //  保存goods 到es！
        this.goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        //  删除： 下架
        this.goodsRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        //  将用户每次访问商品的次数进行 + 1
        //  使用缓存：用哪种数据类型String,List,Set,Hash, {ZSet}，key   如何起名：key = hotScore

        //  定义key ；
        String hotScoreKey = "hotScore";
        //  调用 zincrby hotScore 1 skuId:skuId
        //  返回值就是执行命令的结果
        //  第一个参数：key  第二个参数：
        /*
        public Double incrementScore(K key, V value, double delta) {
            byte[] rawKey = this.rawKey(key);
            byte[] rawValue = this.rawValue(value);
            return (Double)this.execute((connection) -> {
                return connection.zIncrBy(rawKey, delta, rawValue);
            }, true);
        }
          return (Double)this.convertAndReturn(this.delegate.zIncrBy(key, increment, value), this.identityConverter);
         */
        //  ZINCRBY key increment member
        Double count = this.redisTemplate.opsForZSet().incrementScore(hotScoreKey,"skuId:"+skuId,1);

        //  判断
        if (count%10==0){
            //  es---- hotScore
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            this.goodsRepository.save(goods);
        }
    }

    @SneakyThrows
    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        /*
        1.  先准备生产dsl 语句
        2.  执行dsl 语句
        3.  获取到执行结果，将数据封装到SearchResponseVo 对象中
         */
        /*
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchRequest.source(searchSourceBuilder);
         */
        //  获取到查询请求！
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);
        //  执行dsl语句并返回执行的结果对象
        //  SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        SearchResponse searchResponse = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //  将 searchResponse --> SearchResponseVo 前四个属性 在 parseSearchResult 方法中赋值
        SearchResponseVo searchResponseVo = this.parseSearchResult(searchResponse);
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数

        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;
         */

        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  分页公式： 10 3 4 | 9 3 3
        Long totalPages = searchResponseVo.getTotal()%searchResponseVo.getPageSize()==0?searchResponseVo.getTotal()/searchResponseVo.getPageSize():searchResponseVo.getTotal()/searchResponseVo.getPageSize()+1;
        //  另外一种写法！

        //  赋值总页数
        searchResponseVo.setTotalPages(totalPages);
        //  返回的对象
        return searchResponseVo;
    }

    /**
     * 获取es 中的数据
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseSearchResult(SearchResponse searchResponse) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();
         /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
         */
        //  获取返回的结果集
        SearchHits hits = searchResponse.getHits();

        // 设置品牌的对象集合数据 : 品牌数据从哪里来？ 从聚合中获取！
        //  思路：通过tmIdAgg 来获取到 桶，从桶中再获取到数据！
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        //  通过tmIdAgg 来获取到数据
        //  Aggregation 这个对象中不具备获取桶的方法！ Id 的数据类型应该Long
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map((bucket) -> {
            //  创建一个对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  bucket: 相当于集合中的一个对象
            String tmId = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(tmId));

            //  赋值品牌的名称 , 需要将tmNameAgg 转换成 map ,name数据类型应该是String
            ParsedStringTerms tmNameAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //  赋值品牌的url
            ParsedStringTerms tmLogoUrlAgg = (ParsedStringTerms) ((Terms.Bucket) bucket).getAggregations().asMap().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);

            //  将品牌对象返回
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //  设置平台属性集合数据 【nested 数据类型】
        //  先通过map 来获取到attrAgg
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrsList = attrIdAgg.getBuckets().stream().map((bucket) -> {
            //  创建一个对象 SearchResponseAttrVo
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();

            //  平台属性Id
            Number attrId = ((Terms.Bucket) bucket).getKeyAsNumber();
            searchResponseAttrVo.setAttrId(attrId.longValue());

            //  赋值平台属性Name
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //  赋值平台属性值
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
            //  attrValueAgg.getBuckets().stream().map()
            //  本质：通过key 来获取到 对应的平台属性值数据！
            //  List<String> attrValueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            //  获取到桶中的数据！
            //  外面声明一个集合来存储valueName
            List<String> attrValueList = new ArrayList<>();
            List<? extends Terms.Bucket> buckets = attrValueAgg.getBuckets();
            //  循环遍历这个buckets
            for (Terms.Bucket bucket1 : buckets) {
                String valueName = bucket1.getKeyAsString();
                attrValueList.add(valueName);
            }
            //  平台属性值是一个集合数据
            searchResponseAttrVo.setAttrValueList(attrValueList);
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值完成！
        searchResponseVo.setAttrsList(attrsList);

        //  设置商品的集合数据
        SearchHit[] subHits = hits.getHits();

        //  声明一个集合来存储Goods数据
        List<Goods> goodsList = new ArrayList<>();
        //  循环遍历
        for (SearchHit subHit : subHits) {
            //  获取对应的Json 字符串
            String sourceAsString = subHit.getSourceAsString();
            //  将这个Json 字符串变成Goods 对象
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //  细节问题！ 如何用户全文检索，那么name 应该高亮!
            if(subHit.getHighlightFields().get("title")!=null){
                //  按照全文检索的方式检索数据的！ 获取到高亮字段！
                Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                goods.setTitle(title.toString());
            }
            goodsList.add(goods);
        }
        searchResponseVo.setGoodsList(goodsList);
        //  设置总记录数
        searchResponseVo.setTotal(hits.getTotalHits().value);

        return searchResponseVo;
    }

    /**
     * 生产查询请求对象
     * @param searchParam
     * @return
     */
    private SearchRequest buildQueryDsl(SearchParam searchParam) {
        //  第一个入口：分类Id
        //  看做一个查询器 相当 { }
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  需要构建bool { query -- bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  判断一级分类Id
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //  可能按照分类Id 过滤 {query -- bool -- filter -- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }

        //  判断二级分类Id
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //  可能按照分类Id 过滤 {query -- bool -- filter -- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }

        //  判断三级分类Id
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //  可能按照分类Id 过滤 {query -- bool -- filter -- term }
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }

        //  第二个入口：全文检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            //  {query -- bool -- must -- match }
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
        }

        //  还可以通过品牌进行检索： trademark=2:华为
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            //  分割字符串：
            String[] split = trademark.split(":");
            if (split!=null && split.length==2){
                //  品牌Id
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //  平台属性值过滤！
        //  &props=24:128G:机身内存&props=23:8G:运行内存  属性Id：属性值名称：属性名
        String[] props = searchParam.getProps();
        if (props!=null && props.length>0){
            //  循环遍历当前这个数组
            for (String prop : props) {
                // 对其进行分割
                String[] split = prop.split(":");
                //  判断
                if (split!=null && split.length==3){
                    //  创建2个boolQuery
                    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
                    //  里面那一层
                    BoolQueryBuilder subQueryBuilder = QueryBuilders.boolQuery();
                    //  属性Id
                    subQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    subQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));

                    // {must -->nested}
                    queryBuilder.must(QueryBuilders.nestedQuery("attrs",subQueryBuilder, ScoreMode.None));
                    //  将 queryBuilder 放入外层的 boolQueryBuilder
                    boolQueryBuilder.filter(queryBuilder);
                }
            }
        }
        //  {query}
        searchSourceBuilder.query(boolQueryBuilder);

        //  设置高亮显示！
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        //  设置分页
        //  分页公式： 0 ,3  3,3
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize()); // 默认3条数据

        //  排序：综合{hotScore}：order=1:desc  order=1:asc || 价格{price}：order=2:desc  order=2:asc
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //  进行分割
            String[] split = order.split(":");
            if (split!=null && split.length==2){
                //  在此声明一个字段，记录需要排谁！
                String field = "";
                switch (split[0]){
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                //  设置排序
                searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
            }else {
                //  默认排序规则：
                searchSourceBuilder.sort("hotScore",SortOrder.DESC);
            }
        }
        //  品牌聚合：
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));

        //  平台属性聚合：属于 nested 数据类型
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
                .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                        .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                        .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));

        //  细节：设置索引库 GET /goods/_search
        //  设置查询的字段数据显示
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price"},null);

        SearchRequest searchRequest = new SearchRequest("goods");
        /*
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchRequest.source(searchSourceBuilder);
         */
        //  searchSourceBuilder 这个对象其实就是我们写的dsl 语句！
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        //  返回查询请求
        return searchRequest;
    }
}